package com.neusoft.bizcore.webauth.secret;

import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.util.DigestUtils;

import com.neusoft.bizcore.auth.common.bean.UserBean;
import com.neusoft.bizcore.auth.common.jwt.JwtAuthProvider;
import com.neusoft.bizcore.web.bean.LogBean;
import com.neusoft.bizcore.web.dto.result.ResultDTO;
import com.neusoft.bizcore.web.utils.JsonUtils;
import com.neusoft.bizcore.webauth.secret.ding.DingAuthenticationProvider;
import com.neusoft.bizcore.webauth.secret.ding.DingLoginConfigurer;
import com.neusoft.bizcore.webauth.secret.jwt.JwtAuthTokenFilter;
import com.neusoft.bizcore.webauth.secret.oauth2.OAuth2LoginConfigurer;
import com.neusoft.bizcore.webauth.secret.oauth2.Oauth2AuthenticationProvider;
import com.neusoft.bizcore.webauth.secret.userpass.UserPassAuthenticationProvider;
import com.neusoft.bizcore.webauth.secret.userpass.UserPassLoginConfigurer;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Configuration
@EnableWebSecurity
@ComponentScan
public class BizcoreSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private BizcoreWebAuthProperties properties;
    @Autowired
    private SendingLogMessage sendingLogMessage;

    @Autowired
    @Override
    public void configure(final AuthenticationManagerBuilder auth) throws Exception {
        super.configure(auth);
        auth.authenticationProvider(this.dingAuthenticationProvider());
        auth.authenticationProvider(this.userPassAuthenticationProvider());
        auth.authenticationProvider(this.oauth2AuthenticationProvider());
    }

    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        http
                .csrf()
                .disable()

                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .formLogin()
                .disable()
                //---自定义JSON认证逻辑 开始
                .apply(new UserPassLoginConfigurer<>())
                .usernameParameter(this.properties.getUsernameParameter())
                .passwordParameter(this.properties.getPasswordParameter())
                .loginSuccessHandler(this.authenticationSuccessHandler(this.jwtAuthProvider()))
                .loginFailureHandler(this.authenticationFailureHandler())
                .loginProcessesUrl(this.properties.getLoginProcessingUrl())
                .and()
                .apply(new DingLoginConfigurer<>())
                .loginSuccessHandler(this.authenticationSuccessHandler(this.jwtAuthProvider()))
                .loginFailureHandler(this.authenticationFailureHandler())
                .loginProcessesUrl(this.properties.getDing().getLoginProcessingUrl())
                .and()
                .apply(new OAuth2LoginConfigurer<>())
                .loginSuccessHandler(this.authenticationSuccessHandler(this.jwtAuthProvider()))
                .loginFailureHandler(this.authenticationFailureHandler())
                .loginProcessesUrl(this.properties.getOauth2().getLoginProcessingUrl())
                //---自定义JSON认证逻辑 结束
                .and()
                .logout()
                .logoutUrl(this.properties.getLogoutProcessingUrl())
                .logoutSuccessHandler(this.logoutSuccessHandler())
                .permitAll()
                .and()
                .authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS, "/**")
                .permitAll()
                .antMatchers(this.properties.getPermitAll().toArray(new String[0]))
                .permitAll()
                .anyRequest().authenticated();

        if (this.properties.getCas().isEnabled()) {
            http.addFilterBefore(this.casStValidateFilter(), LogoutFilter.class);
        }
        http.addFilterAfter(this.jwtAuthTokenFilter(), LogoutFilter.class);
        http.headers().cacheControl();
    }

    @Override
    public void configure(final WebSecurity web) throws Exception {
        web.ignoring().antMatchers(this.properties.getIgnores().toArray(new String[0]));
        super.configure(web);
    }

    @Bean
    public JwtAuthProvider jwtAuthProvider() {
        return new JwtAuthProvider(this.properties.getJwtExpiration(), this.properties.getJwtSigningKey());
    }

    @Bean
    public JwtAuthTokenFilter jwtAuthTokenFilter() {
        return new JwtAuthTokenFilter(this.jwtAuthProvider());
    }

    @Bean
    public UserPassAuthenticationProvider userPassAuthenticationProvider() {
        return new UserPassAuthenticationProvider();
    }

    @Bean
    public DingAuthenticationProvider dingAuthenticationProvider() {
        return new DingAuthenticationProvider();
    }

    @Bean
    public Oauth2AuthenticationProvider oauth2AuthenticationProvider() {
        return new Oauth2AuthenticationProvider();
    }

    @Bean
    public PasswordEncoder md5PasswordEncoder() {
        return new PasswordEncoder() {
            @Override
            public String encode(final CharSequence charSequence) {
                return DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
            }

            @Override
            public boolean matches(final CharSequence charSequence, final String s) {
                final String encode = DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
                final boolean res = s.equals(encode);
                return res;
            }
        };
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public AuthenticationSuccessHandler authenticationSuccessHandler(final JwtAuthProvider jwtAuthProvider) {
        final AuthenticationSuccessHandler handler = (request, response, authentication) -> {
            BizcoreSecurityConfiguration.log.info("用户[{}]登录成功", authentication.getName());

            final List<String> roles = ((UserBean) authentication.getPrincipal()).getRoles().stream()
                    .map(it -> it.getRole()).collect(Collectors.toList());
            final String token =
                    jwtAuthProvider.generateToken(((UserBean) authentication.getPrincipal()).getUsername(), roles);
            response.setContentType("application/json;charset=UTF-8");
            final ResultDTO<String> result = ResultDTO.success(token);
            response.getWriter().write(JsonUtils.pojoToJson(result));

            // 记录登录日志
            this.sendingLogMessage.sendMsgToLogServer(this.createLogBean(request, authentication, "login"));
        };

        return handler;
    }

    private String createLogBean(final HttpServletRequest request, final Authentication authentication,
            final String operation) {
        final LogBean bean = new LogBean();

        this.setIP(request, bean);
        bean.setOperation(operation);
        bean.setTime(new Date());
        this.setAccountAndUsername(authentication, bean);

        bean.setApp(request.getParameter("app") != null ? request.getParameter("app") : "system-manager");
        bean.setRef(request.getParameter("ref") != null ? request.getParameter("ref") : "");
        bean.setApi(request.getParameter("api") != null ? request.getParameter("api") : "/" + operation);
        bean.setExtra(request.getParameter("extra") != null ? request.getParameter("extra") : "");

        return JsonUtils.pojoToJson(bean);
    }

    private void setAccountAndUsername(final Authentication authentication, final LogBean bean) {
        if (authentication != null) {
            final Object principal = authentication.getPrincipal();
            if (principal instanceof UserBean) {
                final UserBean ub = ((UserBean) principal);
                bean.setAccount(ub.getUsername());
                bean.setUsername(ub.getName());
            }

            if (principal instanceof User) {
                final User ub = ((User) principal);
                bean.setAccount(ub.getUsername());
            }
        }
    }

    private void setIP(final HttpServletRequest request, final LogBean bean) {
        if (request.getHeader("x-forwarded-for") == null) {
            bean.setIp(request.getRemoteAddr());
        } else {
            bean.setIp(request.getHeader("x-forwarded-for"));
        }
    }

    @Bean
    public AuthenticationFailureHandler authenticationFailureHandler() {
        //        final AuthenticationFailureHandler handler = new AuthenticationFailureHandler() {
        //
        //            @Override
        //            public void onAuthenticationFailure(final HttpServletRequest request, final HttpServletResponse response,
        //                    final AuthenticationException exception) throws IOException, ServletException {
        //                response.setStatus(HttpStatus.UNAUTHORIZED.value());
        //                String error = exception.getMessage();
        //                if ((exception instanceof UsernameNotFoundException) || (exception instanceof BadCredentialsException)
        //                        || (exception instanceof DisabledException)) {
        //                    response.setStatus(HttpStatus.UNAUTHORIZED.value());
        //                } else {
        //                    error = "认证失败";
        //                }
        //                response.setContentType("application/json;charset=UTF-8");
        //                final ResultDTO<String> result = ResultDTO.failure(error);
        //                response.getWriter().write(JsonUtils.pojoToJson(result));
        //            }
        //
        //        };

        final AuthenticationFailureHandler handler = (request, response, exception) -> {
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            String error = exception.getMessage();
            final BizcoreAuthenticationException e = (BizcoreAuthenticationException) exception;
            if (exception instanceof BizcoreAuthenticationException) {
                response.setStatus(HttpStatus.UNAUTHORIZED.value());
            } else {
                error = "认证失败";
            }
            response.setContentType("application/json;charset=UTF-8");
            final ResultDTO<String> result = ResultDTO.failure(error);
            response.getWriter().write(JsonUtils.pojoToJson(result));
            // 记录登录异常日志
            this.sendingLogMessage.sendMsgToLogServer(this.createErrorLogBean(request, e));
        };

        return handler;
    }

    private String createErrorLogBean(final HttpServletRequest request, final BizcoreAuthenticationException e) {
        final LogBean bean = new LogBean();

        this.setIP(request, bean);
        bean.setOperation("loginerror");
        bean.setTime(new Date());
        bean.setAccount(e.getAccount());

        bean.setApp(request.getParameter("app") != null ? request.getParameter("app") : "system-manager");
        bean.setRef(request.getParameter("ref") != null ? request.getParameter("ref") : "");
        bean.setApi(request.getParameter("api") != null ? request.getParameter("api") : "/login");
        bean.setExtra(request.getParameter("extra") != null ? request.getParameter("extra") : e.getMessage());

        return JsonUtils.pojoToJson(bean);
    }

    @Bean
    public LogoutSuccessHandler logoutSuccessHandler() {
        final LogoutSuccessHandler handler = (request, response, authentication) -> {
            if (null != authentication) {
                BizcoreSecurityConfiguration.log.info("用户[{}]退出成功", authentication.getName());
                // 记录退出日志
                this.sendingLogMessage.sendMsgToLogServer(this.createLogBean(request, authentication, "logout"));
            }
            response.setContentType("application/json;charset=UTF-8");
            final ResultDTO<Void> result = ResultDTO.success();
            response.getWriter().write(JsonUtils.pojoToJson(result));
        };
        return handler;

    }

    @Bean
    public CasStValidateFilter casStValidateFilter() {
        return new CasStValidateFilter(this.jwtAuthProvider(), this.properties.getCas().getProcessingUrl(),
                this.properties.getCas().getServerUrl(), this.properties.getCas().getValidateStUrl(),
                this.properties.getCas().getHomeUrl());
    }

}
